package com.huqi.aspect;

import com.huqi.json.JsonUtil;
import com.huqi.limiting.TokenBucket;
import com.huqi.log.LoggerUtil;
import com.huqi.result.ResultUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 限流处理切面
 * @author 胡琦
 * @date 2021-07-24 星期六 21:37
 */
@Aspect
@Component
public class ControllerAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAspect.class);

    /**
     * 需要限流的接口
     * 匹配cn.controller包下面的所有方法
     */
    @Pointcut("execution(public * com.huqi.controller.HttpController.deductions(..))")
    public void check(){}

    /**
     * 需要环绕打印日志的接口, 主要是controller
     */
    @Pointcut("execution(public * com.huqi.controller.*.*(..))")
    public void log(){}

    /**
     * 环绕打印日志
     *             //获得类名
     *             String clazzName = joinPoint.getTarget().getClass().getSimpleName();
     *             //获得方法名
     *             String methodName = joinPoint.getSignature().getName();
     *             //获得参数列表
     *             Object[] args = joinPoint.getArgs();
     *
     */
    @Around("log()")
    public Object round(ProceedingJoinPoint proceedingJoinPoint){
        String method = new StringBuilder(proceedingJoinPoint.getSignature().getDeclaringTypeName())
                .append(" -> ")
                .append(proceedingJoinPoint.getSignature().getName())
                .toString();
        if (proceedingJoinPoint.getArgs().length != 0) {
            LoggerUtil.info(LOGGER, "{0}  args = {1}",
                    method, JsonUtil.toJson(proceedingJoinPoint.getArgs()));
        }
        try {
            Object proceed = proceedingJoinPoint.proceed();
            LoggerUtil.info(LOGGER, "{0}  result = {1}", method, JsonUtil.toJson(proceed));
            return proceed;
        } catch (Throwable throwable) {
            LoggerUtil.error(throwable, LOGGER, "round 程序执行异常 ....");
            return ResultUtil.fail(throwable.getMessage());
        }
    }

    /**
     *
     * @param joinPoint
     */
    @Before(value = "check()")
    public void before(JoinPoint joinPoint) throws IllegalAccessException {
        // 尝试去取一个令牌,如果取到了放行,如果没取到就拦截
        boolean token = TokenBucket.getToken();
        if (!token) {
            LoggerUtil.error(LOGGER, "令牌获取失败");
            throw new IllegalAccessException("已经被限流拦截了, 请稍后再试!!!!!");
        }
        LoggerUtil.info(LOGGER, "ControllerAspect 令牌获取到了 method = {0}, args = {1}",
                joinPoint.getSignature().getName(), JsonUtil.toJson(joinPoint.getArgs()));
    }

    /**
     * AOP
     *
         * AOP（Aspect-Oriented Programming，面向切面编程）能够将那些与业务无关，却为业务模块所共同调用的逻辑或责任
         * （例如事务处理、日志管理、权限控制等）封装起来，便于减少系统的重复代码，降低模块间的耦合度，并有利于未来的可扩展性和可维护性。
         * Spring AOP是基于动态代理的，如果要代理的对象实现了某个接口，那么Spring AOP就会使用JDK动态代理去创建代理对象；
         * 而对于没有实现接口的对象，就无法使用JDK动态代理，转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。
     *
     * Spring中的bean生命周期？
         * 1.Bean容器找到配置文件中Spring Bean的定义。
         * 2.Bean容器利用Java Reflection API创建一个Bean的实例。
         * 3.如果涉及到一些属性值，利用set()方法设置一些属性值。
         * 4.如果Bean实现了BeanNameAware接口，调用setBeanName()方法，传入Bean的名字。
         * 5.如果Bean实现了BeanClassLoaderAware接口，调用setBeanClassLoader()方法，传入ClassLoader对象的实例。
         * 6.如果Bean实现了BeanFactoryAware接口，调用setBeanClassFacotory()方法，传入ClassLoader对象的实例。
         * 7.与上面的类似，如果实现了其他*Aware接口，就调用相应的方法。
         * 8.如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象，执行postProcessBeforeInitialization()方法。
         * 9.如果Bean实现了InitializingBean接口，执行afeterPropertiesSet()方法。
         * 10.如果Bean在配置文件中的定义包含init-method属性，执行指定的方法。
         * 11.如果有和加载这个Bean的Spring容器相关的BeanPostProcess对象，执行postProcessAfterInitialization()方法。
         * 12.当要销毁Bean的时候，如果Bean实现了DisposableBean接口，执行destroy()方法。
         * 13.当要销毁Bean的时候，如果Bean在配置文件中的定义包含destroy-method属性，执行指定的方法。
     * 将一个类声明为Spring的bean的注解有哪些？
     * 我们一般使用@Autowired注解去自动装配bean。而想要把一个类标识为可以用@Autowired注解自动装配的bean，可以采用以下的注解实现：
         * 1.@Component注解。通用的注解，可标注任意类为Spring组件。如果一个Bean不知道属于哪一个层，可以使用@Component注解标注。
         * 2.@Repository注解。对应持久层，即Dao层，主要用于数据库相关操作。
         * 3.@Service注解。对应服务层，即Service层，主要涉及一些复杂的逻辑，需要用到Dao层（注入）。
         * 4.@Controller注解。对应Spring MVC的控制层，即Controller层，主要用于接受用户请求并调用Service层的方法返回数据给前端页面。
         * Spring事务管理的方式有几种？
         * 1.编程式事务：在代码中硬编码（不推荐使用）。
         * 2.声明式事务：在配置文件中配置（推荐使用），分为基于XML的声明式事务和基于注解的声明式事务。
     * Spring事务中的隔离级别有哪几种？
         * 在TransactionDefinition接口中定义了五个表示隔离级别的常量：
         * ISOLATION_DEFAULT：使用后端数据库默认的隔离级别，Mysql默认采用的REPEATABLE_READ隔离级别；Oracle默认采用的READ_COMMITTED隔离级别。
         * ISOLATION_READ_UNCOMMITTED：最低的隔离级别，允许读取尚未提交的数据变更，可能会导致脏读、幻读或不可重复读。
         * ISOLATION_READ_COMMITTED：允许读取并发事务已经提交的数据，可以阻止脏读，但是幻读或不可重复读仍有可能发生
         * ISOLATION_REPEATABLE_READ：对同一字段的多次读取结果都是一致的，除非数据是被本身事务自己所修改，可以阻止脏读和不可重复读，但幻读仍有可能发生。
         * ISOLATION_SERIALIZABLE：最高的隔离级别，完全服从ACID的隔离级别。所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，
         * 该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
     * Spring框架中用到了哪些设计模式
         * 1.工厂设计模式：Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。
         * 2.代理设计模式：Spring AOP功能的实现。
         * 3.单例设计模式：Spring中的bean默认都是单例的。
         * 4.模板方法模式：Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类，它们就使用到了模板模式。
         * 5.包装器设计模式：我们的项目需要连接多个数据库，而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
         * 6.观察者模式：Spring事件驱动模型就是观察者模式很经典的一个应用。
         * 7.适配器模式：Spring AOP的增强或通知（Advice）使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
     */


}


